/*
 *  KOS ELF check lsm module
 *
 *  This file is used to validate the permissions of ELF file during its loading.
 *
 *  Authors:  Lianyihong, <lianyihong@ieisystem.com>
 *
 *  Copyright (C) 2023,2024 Inspur, inc
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License version 2,
 *	as published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/lsm_hooks.h>
#include <linux/xattr.h>
#include <linux/fs.h>
#include <linux/binfmts.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/sched.h>

#define ELF_CHECKER_MODULE_NAME			"elf_check"
#define ELF_CHECKER_MAX_ATTR			50
#define ELF_CHECKER_XATTR_NAME			"security.authorization"
#define ELF_CHECKER_XATTR_VALUE_1		"kos"
#define ELF_CHECKER_XATTR_VALUE_2		"mo_kos"
#define ELF_CHECKER_XATTR_VALUE_1_LEN	3
#define ELF_CHECKER_XATTR_VALUE_2_LEN	6

int systemd_count = 0;
int elf_check_flag = 0;


int elf_check_check_flag(struct linux_binprm *bprm) {
	const char* bprm_filename = NULL;

	if (bprm == NULL) {
		return -EPERM;
	}

	bprm_filename = bprm->filename;

	// set flag
	if (strcmp(bprm_filename, "./elf-checker-start.sh") == 0) {
		elf_check_flag = 1;
		printk(KERN_INFO "ELF_CHECKER: Enable ELF checker!");
	} else if (strcmp(bprm_filename, "./elf-checker-stop.sh") == 0) {
		elf_check_flag = 0;
		printk(KERN_INFO "ELF_CHECKER: Disable ELF checker!");
	}

	return 0;
}

int elf_check_bprm_check_security(struct linux_binprm *bprm) {
	struct dentry *dentry = bprm->file->f_path.dentry;
	int size = 0;
	char xattr[ELF_CHECKER_MAX_ATTR];
	const char* bprm_filename = NULL;

	bprm_filename = bprm->filename;
	if (strcmp(bprm_filename, "/usr/lib/systemd/systemd") == 0){
		systemd_count++;
		// printk(KERN_INFO "ELF_CHECKER: [bprm_check_security] systemd_count = %d\n", systemd_count);
	}
	
	// check flag
	if (elf_check_check_flag(bprm) || !elf_check_flag) {
		return 0;
	}

	// ignore initramfs
	if (systemd_count < 1) {
		return 0;
	}

	// initialize xattr
	memset(xattr, '\0', sizeof(xattr));

	// get xattr
	size = vfs_getxattr(dentry, ELF_CHECKER_XATTR_NAME, xattr, ELF_CHECKER_MAX_ATTR);
	if (size == 0) {
		printk(KERN_INFO "ELF_CHECKER: [bprm_check_security] xattr.size = 0\n");
		return -EPERM;
	}
	// validate xattr
	if (strcmp(xattr, ELF_CHECKER_XATTR_VALUE_1) == 0) {
		printk(KERN_INFO "ELF_CHECKER: [bprm_check_security] in file: %s,  check successfully!\n", bprm_filename);
	} else {
		printk(KERN_INFO "ELF_CHECKER: [bprm_check_security] in file: %s  check failed! xattr.security.authorization = %s\n", bprm_filename, xattr);
		return -EPERM;
	}

	return 0;
}

// 一定要有返回值，否则内核会启动不起来
int elf_check_file_permission(struct file *file, int mask) {
	struct inode *inode = NULL;
	struct dentry *dentry = NULL;
	int size = 0;
	char xattr[ELF_CHECKER_MAX_ATTR];
	const char *file_name = file->f_path.dentry->d_iname;

	// ignore initramfs and illegal mask
	if (mask < 0 || systemd_count < 1) {
		return 0;
	}

	// check flag
	if (!elf_check_flag) {
		return 0;
	}

	// initialize xattr
	memset(xattr, '\0', sizeof(xattr));

	// init fields
    inode = file_inode(file);
    dentry = file->f_path.dentry;
    size = vfs_getxattr(dentry, ELF_CHECKER_XATTR_NAME, xattr, ELF_CHECKER_MAX_ATTR);

	// check the '.so' ELF files
	if (strstr(file_name, ".so")) {
		if (size == 0) {
			printk(KERN_INFO "ELF_CHECKER: [file_permission] xattr.size = 0\n");
			return -EPERM;
		}

		// validate xattr
		if (strcmp(xattr, ELF_CHECKER_XATTR_VALUE_1) == 0) {
			printk(KERN_INFO "ELF_CHECKER: [file_permission] in file: %s,  check successfully!\n", file_name);
		} else {
			printk(KERN_INFO "ELF_CHECKER: [file_permission] in file: %s  check failed! xattr.security.authorization = %s\n", file_name, xattr);
			return -EPERM;
		}
	}

	// file writen check 
	if (mask == 2) {
		if (strcmp(xattr, ELF_CHECKER_XATTR_VALUE_1) == 0) {
			vfs_setxattr(dentry, ELF_CHECKER_XATTR_NAME, ELF_CHECKER_XATTR_VALUE_2, ELF_CHECKER_XATTR_VALUE_2_LEN, 0);
			printk(KERN_INFO "ELF_CHECKER: [file_permission] modify file: %s successfully!\n", file_name);
		}
	}

	return 0;
}


static struct security_hook_list elf_check_hooks[] __lsm_ro_after_init = {
	LSM_HOOK_INIT(bprm_check_security, elf_check_bprm_check_security),
	LSM_HOOK_INIT(file_permission, elf_check_file_permission),
};

static __init int elf_check_init(void) {
	// 注意：这里有坑，security.c 中规定 SECURITY_NAME_MAX = 10，之前命名为 "elf_checker" 不生效的原因是超出了长度限制
	if (!security_module_enable(ELF_CHECKER_MODULE_NAME)) {
		printk(KERN_INFO "ELF_CHECKER:  Initializing error.\n");
		return 0;
	}

	security_add_hooks(elf_check_hooks, ARRAY_SIZE(elf_check_hooks), ELF_CHECKER_MODULE_NAME);
	printk(KERN_INFO "ELF_CHECKER:  Initialized.\n");

	return 0;
}

security_initcall(elf_check_init);

